/*-------------------------------------------------------------------------
 *
 * comment.c
 *
 * PostgreSQL 对象注释实用代码。
 *
 * Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/commands/comment.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/table.h"
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_description.h"
#include "catalog/pg_shdescription.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"


/*
 * CommentObject --
 *
 * 此例程用于将关联的注释添加到指定SQL命令所指定对象的
 * pg_description中。
 */
ObjectAddress CommentObject(CommentStmt *fc_stmt)
{
	Relation	fc_relation;
	ObjectAddress fc_address = InvalidObjectAddress;

	/*
	 * 在加载转储时，我们可能会看到数据库旧名称的 COMMENT ON DATABASE。
	 * 报错会阻止 pg_restore 完成（这其实是 pg_restore 的错误，
	 * 但现在我们将在这里绕过这个问题）。共识是最好的解决方法是将错误的
	 * 数据库名称视为警告，而非错误；因此，以下是特殊情况。
	 */
	if (fc_stmt->objtype == OBJECT_DATABASE)
	{
		char	   *fc_database = strVal(fc_stmt->object);

		if (!OidIsValid(get_database_oid(fc_database, true)))
		{
			ereport(WARNING,
					(errcode(ERRCODE_UNDEFINED_DATABASE),
					 errmsg("database \"%s\" does not exist", fc_database)));
			return fc_address;
		}
	}

	/*
	 * 将识别此对象的解析器表示转换为 ObjectAddress。
	 * 如果对象不存在，get_object_address() 将抛出错误，
	 * 并且还会对目标获取锁，以防止并发的 DROP 操作。
	 */
	fc_address = get_object_address(fc_stmt->objtype, fc_stmt->object,
								 &fc_relation, ShareUpdateExclusiveLock, false);

	/* 需要目标对象的所有权。 */
	check_object_ownership(GetUserId(), fc_stmt->objtype, fc_address,
						   fc_stmt->object, fc_relation);

	/* 根据需要执行其他完整性检查。 */
	switch (fc_stmt->objtype)
	{
		case OBJECT_COLUMN:

			/*
			 * 仅允许在表、视图、物化视图、复合类型和外部表的列上添加注释
			 * （这些是 pg_dump 将按列转储注释的唯一 relkinds）。特别是，
			 * 我们希望禁止在索引列上添加注释，因为索引列的命名可能会随着
			 * PG 版本的变化而变化，因此按列转储注释可能会导致重新加载失败。
			 */
			if (fc_relation->rd_rel->relkind != RELKIND_RELATION &&
				fc_relation->rd_rel->relkind != RELKIND_VIEW &&
				fc_relation->rd_rel->relkind != RELKIND_MATVIEW &&
				fc_relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
				fc_relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
				fc_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot set comment on relation \"%s\"",
								RelationGetRelationName(fc_relation)),
						 errdetail_relkind_not_supported(fc_relation->rd_rel->relkind)));
			break;
		default:
			break;
	}

	/*
	 * 数据库、表空间和角色是集群范围内的对象，
	 * 因此对这些对象的任何注释都会记录在共享的 pg_shdescription
	 * 目录中。对所有其他对象的注释都会记录在 pg_description 中。
	 */
	if (fc_stmt->objtype == OBJECT_DATABASE || fc_stmt->objtype == OBJECT_TABLESPACE
		|| fc_stmt->objtype == OBJECT_ROLE)
		CreateSharedComments(fc_address.objectId, fc_address.classId, fc_stmt->comment);
	else
		CreateComments(fc_address.objectId, fc_address.classId, fc_address.objectSubId,
					   fc_stmt->comment);

	/*
	 * 如果 get_object_address() 为我们打开了关系，
	 * 我们会关闭它以保持引用计数的正确性
	 * - 但我们会在提交时保留 get_object_address() 获取的任何锁，
	 * 以防止并发活动。
	 */
	if (fc_relation != NULL)
		relation_close(fc_relation, NoLock);

	return fc_address;
}

/*
 * CreateComments --
 *
 * 为指定的对象描述符创建注释。插入新的
 * pg_description 元组，或用相同键替换现有元组。
 *
 * 如果给定的注释为 null 或空字符串，则删除
 * 指定键的任何现有注释。
 */
void CreateComments(Oid fc_oid, Oid fc_classoid, int32 fc_subid, const char *fc_comment)
{
	Relation	fc_description;
	ScanKeyData fc_skey[3];
	SysScanDesc fc_sd;
	HeapTuple	fc_oldtuple;
	HeapTuple	fc_newtuple = NULL;
	Datum		fc_values[Natts_pg_description];
	bool		fc_nulls[Natts_pg_description];
	bool		fc_replaces[Natts_pg_description];
	int			fc_i;

	/* 将空字符串减少为 NULL 情况 */
	if (fc_comment != NULL && strlen(fc_comment) == 0)
		fc_comment = NULL;

	/* 准备形成或更新元组（如有必要） */
	if (fc_comment != NULL)
	{
		for (fc_i = 0; fc_i < Natts_pg_description; fc_i++)
		{
			fc_nulls[fc_i] = false;
			fc_replaces[fc_i] = true;
		}
		fc_values[Anum_pg_description_objoid - 1] = ObjectIdGetDatum(fc_oid);
		fc_values[Anum_pg_description_classoid - 1] = ObjectIdGetDatum(fc_classoid);
		fc_values[Anum_pg_description_objsubid - 1] = Int32GetDatum(fc_subid);
		fc_values[Anum_pg_description_description - 1] = CStringGetTextDatum(fc_comment);
	}

	/* 使用索引搜索匹配的旧元组 */

	ScanKeyInit(&fc_skey[0],
				Anum_pg_description_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_oid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_description_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_classoid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_description_objsubid,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum(fc_subid));

	fc_description = table_open(DescriptionRelationId, RowExclusiveLock);

	fc_sd = systable_beginscan(fc_description, DescriptionObjIndexId, true,
							NULL, 3, fc_skey);

	while ((fc_oldtuple = systable_getnext(fc_sd)) != NULL)
	{
		/* 找到了旧元组，所以删除或更新它 */

		if (fc_comment == NULL)
			CatalogTupleDelete(fc_description, &fc_oldtuple->t_self);
		else
		{
			fc_newtuple = heap_modify_tuple(fc_oldtuple, RelationGetDescr(fc_description), fc_values,
										 fc_nulls, fc_replaces);
			CatalogTupleUpdate(fc_description, &fc_oldtuple->t_self, fc_newtuple);
		}

		break;					/* 假设只能有一个匹配 */
	}

	systable_endscan(fc_sd);

	/* 如果未找到旧元组，则插入新的 */

	if (fc_newtuple == NULL && fc_comment != NULL)
	{
		fc_newtuple = heap_form_tuple(RelationGetDescr(fc_description),
								   fc_values, fc_nulls);
		CatalogTupleInsert(fc_description, fc_newtuple);
	}

	if (fc_newtuple != NULL)
		heap_freetuple(fc_newtuple);

	/* 完成 */

	table_close(fc_description, NoLock);
}

/*
 * CreateSharedComments --
 *
 * 为指定的共享对象描述符创建注释。插入新的
 * pg_shdescription 元组，或用相同键替换现有元组。
 *
 * 如果给定的注释为 null 或空字符串，则删除
 * 指定键的任何现有注释。
 */
void CreateSharedComments(Oid fc_oid, Oid fc_classoid, const char *fc_comment)
{
	Relation	fc_shdescription;
	ScanKeyData fc_skey[2];
	SysScanDesc fc_sd;
	HeapTuple	fc_oldtuple;
	HeapTuple	fc_newtuple = NULL;
	Datum		fc_values[Natts_pg_shdescription];
	bool		fc_nulls[Natts_pg_shdescription];
	bool		fc_replaces[Natts_pg_shdescription];
	int			fc_i;

	/* 将空字符串减少为 NULL 情况 */
	if (fc_comment != NULL && strlen(fc_comment) == 0)
		fc_comment = NULL;

	/* 准备形成或更新元组（如有必要） */
	if (fc_comment != NULL)
	{
		for (fc_i = 0; fc_i < Natts_pg_shdescription; fc_i++)
		{
			fc_nulls[fc_i] = false;
			fc_replaces[fc_i] = true;
		}
		fc_values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(fc_oid);
		fc_values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(fc_classoid);
		fc_values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(fc_comment);
	}

	/* 使用索引搜索匹配的旧元组 */

	ScanKeyInit(&fc_skey[0],
				Anum_pg_shdescription_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_oid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_shdescription_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_classoid));

	fc_shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);

	fc_sd = systable_beginscan(fc_shdescription, SharedDescriptionObjIndexId, true,
							NULL, 2, fc_skey);

	while ((fc_oldtuple = systable_getnext(fc_sd)) != NULL)
	{
		/* 找到了旧元组，所以删除或更新它 */

		if (fc_comment == NULL)
			CatalogTupleDelete(fc_shdescription, &fc_oldtuple->t_self);
		else
		{
			fc_newtuple = heap_modify_tuple(fc_oldtuple, RelationGetDescr(fc_shdescription),
										 fc_values, fc_nulls, fc_replaces);
			CatalogTupleUpdate(fc_shdescription, &fc_oldtuple->t_self, fc_newtuple);
		}

		break;					/* 假设只能有一个匹配 */
	}

	systable_endscan(fc_sd);

	/* 如果未找到旧元组，则插入新的 */

	if (fc_newtuple == NULL && fc_comment != NULL)
	{
		fc_newtuple = heap_form_tuple(RelationGetDescr(fc_shdescription),
								   fc_values, fc_nulls);
		CatalogTupleInsert(fc_shdescription, fc_newtuple);
	}

	if (fc_newtuple != NULL)
		heap_freetuple(fc_newtuple);

	/* 完成 */

	table_close(fc_shdescription, NoLock);
}

/*
 * DeleteComments -- 为对象删除注释
 *
 * 如果 subid 不为零，则仅删除与其匹配的注释。
 * 如果 subid 为零，则删除所有与 oid/classoid 匹配的注释
 * （这对应于删除整个对象）。
 */
void DeleteComments(Oid fc_oid, Oid fc_classoid, int32 fc_subid)
{
	Relation	fc_description;
	ScanKeyData fc_skey[3];
	int			fc_nkeys;
	SysScanDesc fc_sd;
	HeapTuple	fc_oldtuple;

	/* 使用索引搜索所有匹配的旧元组 */

	ScanKeyInit(&fc_skey[0],
				Anum_pg_description_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_oid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_description_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_classoid));

	if (fc_subid != 0)
	{
		ScanKeyInit(&fc_skey[2],
					Anum_pg_description_objsubid,
					BTEqualStrategyNumber, F_INT4EQ,
					Int32GetDatum(fc_subid));
		fc_nkeys = 3;
	}
	else
		fc_nkeys = 2;

	fc_description = table_open(DescriptionRelationId, RowExclusiveLock);

	fc_sd = systable_beginscan(fc_description, DescriptionObjIndexId, true,
							NULL, fc_nkeys, fc_skey);

	while ((fc_oldtuple = systable_getnext(fc_sd)) != NULL)
		CatalogTupleDelete(fc_description, &fc_oldtuple->t_self);

	/* 完成 */

	systable_endscan(fc_sd);
	table_close(fc_description, RowExclusiveLock);
}

/*
 * DeleteSharedComments -- 为共享对象删除注释
 */
void DeleteSharedComments(Oid fc_oid, Oid fc_classoid)
{
	Relation	fc_shdescription;
	ScanKeyData fc_skey[2];
	SysScanDesc fc_sd;
	HeapTuple	fc_oldtuple;

	/* 使用索引搜索所有匹配的旧元组 */

	ScanKeyInit(&fc_skey[0],
				Anum_pg_shdescription_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_oid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_shdescription_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_classoid));

	fc_shdescription = table_open(SharedDescriptionRelationId, RowExclusiveLock);

	fc_sd = systable_beginscan(fc_shdescription, SharedDescriptionObjIndexId, true,
							NULL, 2, fc_skey);

	while ((fc_oldtuple = systable_getnext(fc_sd)) != NULL)
		CatalogTupleDelete(fc_shdescription, &fc_oldtuple->t_self);

	/* 完成 */

	systable_endscan(fc_sd);
	table_close(fc_shdescription, RowExclusiveLock);
}

/*
 * GetComment -- 获取对象的注释，如果未找到则返回 null。
 */
char * GetComment(Oid fc_oid, Oid fc_classoid, int32 fc_subid)
{
	Relation	fc_description;
	ScanKeyData fc_skey[3];
	SysScanDesc fc_sd;
	TupleDesc	fc_tupdesc;
	HeapTuple	fc_tuple;
	char	   *fc_comment;

	/* 使用索引搜索匹配的旧元组 */

	ScanKeyInit(&fc_skey[0],
				Anum_pg_description_objoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_oid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_description_classoid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_classoid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_description_objsubid,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum(fc_subid));

	fc_description = table_open(DescriptionRelationId, AccessShareLock);
	fc_tupdesc = RelationGetDescr(fc_description);

	fc_sd = systable_beginscan(fc_description, DescriptionObjIndexId, true,
							NULL, 3, fc_skey);

	fc_comment = NULL;
	while ((fc_tuple = systable_getnext(fc_sd)) != NULL)
	{
		Datum		fc_value;
		bool		fc_isnull;

		/* 找到元组，获取描述字段 */
		fc_value = heap_getattr(fc_tuple, Anum_pg_description_description, fc_tupdesc, &fc_isnull);
		if (!fc_isnull)
			fc_comment = TextDatumGetCString(fc_value);
		break;					/* 假设只能有一个匹配 */
	}

	systable_endscan(fc_sd);

	/* 完成 */
	table_close(fc_description, AccessShareLock);

	return fc_comment;
}
